/**
 * 
 */
package truerefactor.refactor;

import java.util.ArrayList;
import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;
import truerefactor.graph.StatementNode;
import truerefactor.graph.StatementType;

/**
 * ReplaceTempWithQuery - An implementation of the Replace Temp With Query
 * refactoring technique.
 * 
 * @author Isaac Griffith
 */
public class ReplaceTempWithQuery extends Refactoring {

    /**
     * Creates a new ReplaceTempWithQuery instance
     */
    public ReplaceTempWithQuery()
    {
        this.shortName = "RTWQ";
        this.name = "Replace Temp with Query";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeGraph )
     */
    @Override
    public void execute(CodeGraph graph)
    {
        MethodNode method = (MethodNode) operands.get(0);
        // 1. Look for a temporary variable that is assigned to once
        List<StatementNode> tempVars = findTempVariables(method);
        for (StatementNode temp : tempVars)
        {
            List<StatementNode> tempAssignments = findTempAssignments(temp);

            // if tempAssignments.size() > 1 use Split Temporary Variable

            // 2. Declare the temp as final to ensure only one assignment
            temp.setCode("final " + temp.getCode());

            // 3. Extract the right hand side of the assignment into a method
            MethodNode mNode = extractRHS(temp, tempAssignments.get(0));
            mNode.setAccessiblity(Accessibility.Private);

            ClassNode source = temp.getParent().getParentClass();
            source.addMethod(mNode);
            graph.insert(mNode);
            graph.insert(new Edge(source, mNode, EdgeType.Method));

            // Ensure free from side effects
            removeSideEffects(mNode);

            // 4. Use Inline Temp on the temp
            CodeNode operands[] = { temp };
            Refactoring next = RefactoringFactory.getInstance().createInlineTemp(operands);
            next.execute(graph);
        }
    }

    /**
     * Removes side effects of the provided method
     * 
     * @param mNode
     *            method node to adjust
     */
    private void removeSideEffects(MethodNode mNode)
    {
        // in order to use separate query from modifier we need to be able to
        // chain refactorings together.
    }

    /**
     * Extracts the right hand side of an assignment stored in the provided
     * StatementNode
     * 
     * @param temp
     *            StatementNode containing the temporary variable name
     * @param assign
     *            StatmeentNode containing the initial assingment
     * @return newly created MethodNode representing an assignment call.
     */
    private MethodNode extractRHS(StatementNode temp, StatementNode assign)
    {
        MethodNode mNode = new MethodNode("get" + temp.getCode().substring(temp.getCode().indexOf(" ")), "");

        String code = assign.getCode();
        code = code.substring(code.indexOf("="));
        StatementNode sNode = new StatementNode(mNode, StatementType.EXPRESSION, "return " + code);
        mNode.getStatementGraph().insert(sNode);
        mNode.getStatements().add(sNode);

        return mNode;
    }

    /**
     * Finds all temporary (local) variables within a method statement graph.
     * 
     * @param method
     *            method to parse for temporary variables
     * @return List of statement nodes containing the defining expressions for
     *         each temporary variable found.
     */
    private List<StatementNode> findTempVariables(MethodNode method)
    {
        List<CodeNode> stmts = method.getStatements();
        List<StatementNode> retVal = new ArrayList<StatementNode>();

        for (CodeNode stmt : stmts)
        {
            if (stmt instanceof StatementNode)
            {
                if (stmt.getIdentifier().equals(StatementType.EXPRESSION.getIdentifier()))
                {
                    String line = stmt.getCode();
                    if (line.indexOf("=") < 0)
                    {
                        retVal.add((StatementNode) stmt);
                    }
                    else if (line.indexOf(" ") < line.indexOf("=") - 2)
                    {
                        retVal.add((StatementNode) stmt);
                    }
                }
            }
        }

        return retVal;
    }

    /**
     * @param temp
     * @return
     */
    private List<StatementNode> findTempAssignments(StatementNode temp)
    {
        List<StatementNode> retVal = new ArrayList<StatementNode>();
        List<CodeNode> stmts = temp.getParent().getStatements();
        String identifier = temp.getCode().substring(temp.getCode().indexOf(" ") + 1);

        if (identifier.indexOf("=") >= 0 && identifier.contains(";"))
        {
            identifier = identifier.substring(0, identifier.indexOf(";"));
        }
        else if (identifier.indexOf("=") >= 0 && identifier.contains(","))
        {
            identifier = identifier.substring(0, identifier.indexOf(","));
        }

        for (CodeNode stmt : stmts)
        {
            if (stmt instanceof StatementNode)
            {
                String code = stmt.getCode();
                if (code.contains(identifier) && code.contains("="))
                {
                    retVal.add((StatementNode) stmt);
                }
            }
        }

        return retVal;
    }
}
